package com.huangfuyi.process.service;

import com.huangfuyi.process.actuator.enums.EPModelStatus;
import com.huangfuyi.process.actuator.enums.EPNodeType;
import com.huangfuyi.process.dao.EPCoreModelDao;
import com.huangfuyi.process.dao.EPCoreNodeDao;
import com.huangfuyi.process.entity.model.EPModelEntity;
import com.huangfuyi.process.entity.node.EPNodeEntity;
import com.huangfuyi.process.entity.node.EPNodeNextConfigEntity;
import com.huangfuyi.process.entity.node.EPNodeWithNextConfigEntity;
import com.huangfuyi.process.exceptions.EPDaoException;
import com.huangfuyi.process.exceptions.EPServiceException;
import com.huangfuyi.process.managers.EPManagers;
import com.huangfuyi.process.managers.EPNodeAppManager;
import com.huangfuyi.process.utils.EPIdUtils;
import com.huangfuyi.process.utils.EPObjectUtils;

import java.util.*;

public class EPCoreModelService<T extends EPModelEntity, E extends EPNodeEntity, V extends EPNodeNextConfigEntity> {

    private final EPCoreModelDao<T> modelDao;

    private final EPCoreNodeDao<E, V> nodeDao;

    /**
     * 新增流程模板
     */
    public void add(EPModelEntity modelInfo, List<? extends EPNodeWithNextConfigEntity<? extends EPNodeNextConfigEntity>> nodeInfo) {
        // 流程模板配置及节点配置信息检查和补全
        EPModelEntity checkedModelInfo = modelInfoCheckAndFormatForAdd(modelInfo);
        List<? extends EPNodeWithNextConfigEntity<? extends EPNodeNextConfigEntity>> checkedNodeInfo = nodeInfoCheckAndFormatForAdd(nodeInfo, checkedModelInfo.getId());

        // 简单检查流程走向配置是否存在非业务性错误（如：死循环）
        List<? extends EPNodeNextConfigEntity> nextConfigs = nextConfigCheckAndFormat(nodeInfo, checkedModelInfo.getId());
        int nextConfigAdded = nodeDao.addAllNextConfigs(nextConfigs);
        if (nextConfigAdded != nextConfigs.size()) {
            nodeDao.deleteAllNextConfigsByModelId(checkedModelInfo.getId());
            throw new EPDaoException(String.format("流程模板的节点指向配置信息没有全部持久化成功。流程模板名称： %s", checkedModelInfo.getName()));
        }

        // 保存流程节点的配置信息
        int nodeAdded = nodeDao.addAll(checkedNodeInfo);
        if (nodeAdded != checkedNodeInfo.size()) {
            nodeDao.deleteAllByModelId(checkedModelInfo.getId());
            nodeDao.deleteAllNextConfigsByModelId(checkedModelInfo.getId());
            throw new EPDaoException(String.format("流程模板的节点信息没有全部持久化成功。流程模板名称： %s", checkedModelInfo.getName()));
        }

        // 保存流程模板基础信息
        int modelAdded = modelDao.add(checkedModelInfo);
        if (modelAdded <= 0) {
            nodeDao.deleteAllByModelId(checkedModelInfo.getId());
            nodeDao.deleteAllNextConfigsByModelId(checkedModelInfo.getId());
            throw new EPDaoException(String.format(Locale.ROOT, "流程模板的基础信息持久化失败。流程模板名称： %s", checkedModelInfo.getName()));
        }

    }

    // 流程模板基础信息检查和补全
    private EPModelEntity modelInfoCheckAndFormatForAdd(EPModelEntity modelInfo) {
        if (EPObjectUtils.isEmpty(modelInfo)) {
            throw new EPServiceException("新增流程模板时，流程模板信息为空。");
        }

        if (EPObjectUtils.isEmpty(modelInfo.getName())) {
            throw new EPServiceException("新增流程模板时，流程模板名称为空。");
        }

        if (EPObjectUtils.isEmpty(modelInfo.getId())) {
            modelInfo.setId(EPIdUtils.getId());
        }

        if (EPObjectUtils.isEmpty(modelInfo.getStatus())) {
            modelInfo.setStatus(EPModelStatus.DISABLE);
        }

        modelInfo.setCreateTime(System.currentTimeMillis());
        return modelInfo;
    }

    private List<? extends EPNodeWithNextConfigEntity<? extends EPNodeNextConfigEntity>> nodeInfoCheckAndFormatForAdd(List<? extends EPNodeWithNextConfigEntity<? extends EPNodeNextConfigEntity>> nodeInfo, String modelId) {
        if (EPObjectUtils.isEmpty(nodeInfo)) {
            throw new EPServiceException("新增流程模板时，流程节点信息为空。");
        }

        EPNodeAppManager nodeAppManager = EPManagers.getNodeAppManager();
        for (EPNodeWithNextConfigEntity<? extends EPNodeNextConfigEntity> n : nodeInfo) {
            if (EPObjectUtils.isEmpty(n.getId())) {
                throw new EPServiceException(String.format(Locale.ROOT, "新增流程模板时，流程节点的 id 配置为空。当前节点的配置信息为： %s", n));
            }

            if (EPObjectUtils.isEmpty(n.getNodeType()) || !EPNodeType.contains(n.getNodeType())) {
                throw new EPServiceException(String.format(Locale.ROOT, "新增流程模板时，无法识别配置的节点类型：%s，节点id： %s", n.getNodeType(), n.getId()));
            }

            if (!EPObjectUtils.isEmpty(nodeAppManager) && EPObjectUtils.isEmpty(nodeAppManager.getApp(n.getApplication()))) {
                throw new EPServiceException(String.format(Locale.ROOT, "APP管理器中无法获得节点配置的APP实现，APP配置: %s， 节点id：%s", n.getApplication(), n.getId()));
            }

            n.setModelId(modelId);
        }

        return nodeInfo;
    }

    private List<? extends EPNodeNextConfigEntity> nextConfigCheckAndFormat(List<? extends EPNodeWithNextConfigEntity<? extends EPNodeNextConfigEntity>> nodeInfo, String modelId) {

        EPNodeWithNextConfigEntity<? extends EPNodeNextConfigEntity> start = null;

        // 寻找并验证有且仅有一个start节点
        // 检查节点id是否唯一
        HashMap<String, EPNodeWithNextConfigEntity<? extends EPNodeNextConfigEntity>> nodeIdMapping = new HashMap<>();
        HashSet<String> existNodeId = new HashSet<>();
        for (EPNodeWithNextConfigEntity<? extends EPNodeNextConfigEntity> nodeConfig : nodeInfo) {
            if (existNodeId.contains(nodeConfig.getId())) {
                throw new EPServiceException(String.format(Locale.ROOT, "流程节点id配置不唯一。id：%s", nodeConfig.getId()));
            }
            existNodeId.add(nodeConfig.getId());

            if (EPNodeType.START.equals(nodeConfig.getNodeType())) {
                if (EPObjectUtils.isEmpty(start)) {
                    start = nodeConfig;
                } else {
                    throw new EPServiceException(String.format(Locale.ROOT, "流程模板中存在两个或以上开始节点。节点id为： %s;  %s", start.getId(), nodeConfig.getId()));
                }
            }
            nodeIdMapping.put(nodeConfig.getId(), nodeConfig);
        }

        if (EPObjectUtils.isEmpty(start)) {
            throw new EPServiceException("当前流程模板中缺少开始节点。");
        }

        // 流程走向逻辑检查
        logicCheck(start, nodeIdMapping, new HashSet<>());

        ArrayList<EPNodeNextConfigEntity> checkedNextConfig = new ArrayList<>();
        for (EPNodeWithNextConfigEntity<? extends EPNodeNextConfigEntity> node : nodeInfo) {
            List<? extends EPNodeNextConfigEntity> nextConfigs = node.getNextConfig();
            if (EPObjectUtils.isEmpty(nextConfigs)) {
                continue;
            }

            for (EPNodeNextConfigEntity nextConfig : nextConfigs) {
                nextConfig.setNodeId(node.getId());
                nextConfig.setModelId(modelId);
            }

            checkedNextConfig.addAll(nextConfigs);
        }
        return checkedNextConfig;
    }

    // 检查流程内是否有死循环
    // 检查流程内是否所有节点最终都指向结束节点
    private void logicCheck(EPNodeWithNextConfigEntity<? extends EPNodeNextConfigEntity> node, HashMap<String, EPNodeWithNextConfigEntity<? extends EPNodeNextConfigEntity>> nodeIdMapping, HashSet<String> currentPath) {
        List<? extends EPNodeNextConfigEntity> nextConfig = node.getNextConfig();

        if (EPObjectUtils.isEmpty(nextConfig) && !EPNodeType.END.equals(node.getNodeType())) {
            throw new EPServiceException(String.format(Locale.ROOT, "节点%s缺少指向配置信息。", node.getId()));
        }

        for (EPNodeNextConfigEntity next : nextConfig) {
            // 若走过的路径中，存在当前节点，说明出现了循环
            // 如果当前节点的指向配置数量大于1，说明存在走出循环的可能
            // 如果当前节点的指向配置数量等于1，则需要后续节点中，至少有一个节点的指向配置数量大于1，则存在跳出循环的可能，否则为死循环
            if (currentPath.contains(next.getPointNodeId())) {
                if (nextConfig.size() == 1 && !findBranch(node, nodeIdMapping)) {
                    throw new EPServiceException(String.format(Locale.ROOT, "从节点%s开始，可能存在死循环", node.getId()));
                } else {
                    continue;
                }
            }

            EPNodeWithNextConfigEntity<? extends EPNodeNextConfigEntity> nextNode = nodeIdMapping.get(next.getPointNodeId());
            if (EPObjectUtils.isEmpty(nextNode)) {
                throw new EPServiceException(String.format(Locale.ROOT, "节点%s的指向配置中，指向的节点%s不存在。", node.getId(), next.getPointNodeId()));
            }
            if (EPNodeType.START.equals(nextNode.getNodeType())) {
                throw new EPServiceException(String.format(Locale.ROOT, "节点%s的指向配置中，指向了开始节点。", node.getId()));
            } else {
                HashSet<String> newPath = new HashSet<>(currentPath);
                newPath.add(next.getPointNodeId());
                logicCheck(nextNode, nodeIdMapping, newPath);
            }
        }
    }

    // 节点出现在循环中，说明循环中的节点的指向配置至少有1个
    // 在后续节点中寻找指向配置数量大于1的节点
    private boolean findBranch(EPNodeWithNextConfigEntity<? extends EPNodeNextConfigEntity> node, HashMap<String, EPNodeWithNextConfigEntity<? extends EPNodeNextConfigEntity>> nodeIdMapping) {
        String startNodeId = node.getId();
        while (true) {
            List<? extends EPNodeNextConfigEntity> nextNodes = node.getNextConfig();
            if (nextNodes.size() > 1) {
                return true;
            } else if (nextNodes.size() == 1) {
                // 沿指向配置寻找时，一直没有找到存在分支的节点，且又回到了产生循环的起始节点，说明循环必定为死循环
                node = nodeIdMapping.get(nextNodes.get(0).getPointNodeId());
                if (node.getId().equals(startNodeId)) {
                    return false;
                }
            }
        }
    }

    /**
     * 通过流程模板id查询
     */
    public T queryModelById(String modelId) {
        if (EPObjectUtils.isEmpty(modelId)) {
            throw new EPServiceException("流程模板id不能为空。");
        }

        T modelInfo = modelDao.queryById(modelId);

        if (EPObjectUtils.isEmpty(modelInfo)) {
            throw new EPDaoException(String.format(Locale.ROOT, "查不到id为：%s 的流程模板。", modelId));
        }

        return modelInfo;
    }

    /**
     * 通过流程模板的名称查询最新版本的流程模板信息
     */
    public T queryLastVersionModelByName(String modelName) {
        if (EPObjectUtils.isEmpty(modelName)) {
            throw new EPServiceException("流程模板名称不能为空。");
        }

        T modelInfo = modelDao.queryLastVersionByName(modelName);

        if (EPObjectUtils.isEmpty(modelInfo)) {
            throw new EPDaoException(String.format(Locale.ROOT, "查不到名称为：%s 的流程模板。", modelName));
        }

        return modelInfo;
    }

    /**
     * 查询流程的开始节点
     */
    public E queryStartNode(String modelId) {
        if (EPObjectUtils.isEmpty(modelId)) {
            throw new EPServiceException("流程模板id不能为空。");
        }

        List<E> nodes = nodeDao.queryByModelId(modelId);
        if (EPObjectUtils.isEmpty(nodes)) {
            throw new EPDaoException(String.format(Locale.ROOT, "查不到流程模板（id:%s） 的节点信息。", modelId));
        }

        for (E node : nodes) {
            if (EPNodeType.START.equals(node.getNodeType())) {
                return node;
            }
        }

        throw new EPDaoException(String.format(Locale.ROOT, "查不到流程模板（id:%s） 的开始节点信息。", modelId));
    }

    /**
     * 通过id查询流程节点信息
     */
    public E queryNodeById(String modelId, String nodeId) {
        if (EPObjectUtils.isEmpty(modelId)) {
            throw new EPServiceException("流程模板id不能为空。");
        }

        if (EPObjectUtils.isEmpty(nodeId)) {
            throw new EPServiceException("流程节点id不能为空。");
        }

        E nodeInfo = nodeDao.queryById(modelId, nodeId);

        if (EPObjectUtils.isEmpty(nodeInfo)) {
            throw new EPDaoException(String.format(Locale.ROOT, "查不到指定的节点信息，流程模板id：%s； 节点id：%s", modelId, nodeId));
        }
        return nodeInfo;
    }

    /**
     * 通过id查询流程节点的指向信息
     */
    public List<V> queryNextConfigByNodeId(String modelId, String nodeId) {
        if (EPObjectUtils.isEmpty(modelId)) {
            throw new EPServiceException("流程模板id不能为空。");
        }

        if (EPObjectUtils.isEmpty(nodeId)) {
            throw new EPServiceException("流程节点id不能为空。");
        }

        List<V> nextConfigs = nodeDao.queryNextConfigByNodeId(modelId, nodeId);

        if (EPObjectUtils.isEmpty(nextConfigs)) {
            throw new EPDaoException(String.format(Locale.ROOT, "查不到指定的节点的指向配置信息，流程模板id：%s； 节点id：%s", modelId, nodeId));
        }
        return nextConfigs;
    }

    public EPCoreModelService(EPCoreModelDao<T> modelDao, EPCoreNodeDao<E, V> nodeDao) {
        this.modelDao = modelDao;
        this.nodeDao = nodeDao;
    }
}
